Using Javascript in IPython Notebooks

andy@payne.org
Updated: March 11, 2016

These are my notes on using Javascript inside of the iPython Web Notebook.

Getting Started

The easiest way to access Javascript is with the %%javascript cell magic:


In [1]:
%%javascript
console.log("Hello World!")


(To view the Javascript console in Chrome & Firefox, use CONTROL+SHIFT+J).

Accessing the Cell Output Area

The element Javascript variable refers to the current cell. For example:


In [2]:
%%javascript
element.append("Hello World!");


(NOTE: prior to version 2.0, there was a container variable, but the current docs refer to an element variable. For more information, see the source: outputarea.js).

You can also use the Javascript() object, creates an object that will be rendered as Javascript by the IPython display system:


In [3]:
from IPython.display import Javascript
Javascript("element.append('Hello World, Again!');")


Out[3]:

A lower-level approach is to implement a _repr_javascript_() object method that returns Javascript. This lets you add Javascript rendering to any object:


In [4]:
class HelloWorld():
    def _repr_javascript_(self):
        return "element.append('HelloWorld class');"
    
hw = HelloWorld()
hw


Out[4]:

Global Variables

It's sometimes handy to define a global variable or function that can be used in later Javascript cells. Attributes added to the windows object are available as global variables:


In [5]:
%%javascript
window.foovar = 42;



In [6]:
%%javascript
element.append(foovar);


Loading External Javascript Libraries

You often need to load other Javascript libraries before your code is run. The lib argument to the Javascript() display object specifies required libraries:


In [7]:
libs = ["https://cdnjs.cloudflare.com/ajax/libs/three.js/r74/three.js"]
Javascript("""element.append(THREE.REVISION);""", lib=libs)


Out[7]:

NOTE: libraries are reloaded each time the cell is run. The underlying implementation, as of IPython 2.1, uses jQuery's getScript() method to load each library in the order specified.

This behavior will break any running event loops (such as an animate event used with THREE.js), because the old event loop will be running with objects defined from the first library load, and the variables will get overwritten with new types based on the second library load.

For example, in this THREE.js example, the animate() function is called 60 times/second to render the scene and rotate the cube. When the cell is reloaded, the call to render.render(scene, camera) will fail:


In [8]:
libs = ["https://cdnjs.cloudflare.com/ajax/libs/three.js/r74/three.js"]
Javascript("""
    var renderer = new THREE.WebGLRenderer({ antialias: true });
    var canvas = renderer.domElement;
    element.append(canvas);
    var camera = new THREE.PerspectiveCamera(70, canvas.width / canvas.height, 1, 1000);
    camera.position.z = 400;
    var scene = new THREE.Scene();
    var material = new THREE.MeshDepthMaterial();
    var mesh = new THREE.Mesh(new THREE.CubeGeometry(200, 200, 200), material);
    scene.add(mesh);
    function animate() {
        renderer.render(scene, camera);
        mesh.rotation.x += 0.005;
        mesh.rotation.y += 0.01;
        requestAnimationFrame(animate);
    }
    animate();
""", lib=libs)


Out[8]:

Because of this, the animation event loop will stop when the library is reloaded. For example, executing the following cell will cause the rotating cube to stop:


In [9]:
libs = ["https://cdnjs.cloudflare.com/ajax/libs/three.js/r74/three.js"]
Javascript("""console.log(THREE);""", lib=libs)


Out[9]:

A better approach is to use require.js to load external modules, which is supported starting in IPython 2.0. First, define the paths to the different modules:


In [10]:
%%javascript
require.config({
    paths: {
        'three': "https://cdnjs.cloudflare.com/ajax/libs/three.js/r74/three",
    }
});


Now, use the require() function to wrap your code, specifying the libraries you need:


In [11]:
%%javascript
require(['three'], function() {
    element.append(THREE.REVISION);
});


Using the previous example:


In [12]:
%%javascript
var renderer = new THREE.WebGLRenderer({ antialias: true });
var canvas = renderer.domElement;
element.append(canvas);
var camera = new THREE.PerspectiveCamera(70, canvas.width / canvas.height, 1, 1000);
camera.position.z = 400;
var scene = new THREE.Scene();
var material = new THREE.MeshDepthMaterial();
var mesh = new THREE.Mesh(new THREE.CubeGeometry(200, 200, 200), material);
scene.add(mesh);
function animate() {
    renderer.render(scene, camera);
    mesh.rotation.x += 0.005;
    mesh.rotation.y += 0.01;
    requestAnimationFrame(animate);
}
animate();


And now reloading doesn't break the animation event loop:


In [13]:
%%javascript
require(['three'], function() {
    element.append(THREE.REVISION);
});


Generating Javascript from Python

You often want to generate Javascript from Python.

Here's a hack that takes a fragment of Javascript, finds all the functions that are declared, and generates bound Python methods for each function:


In [14]:
import types
from functools import partial
import json

class JSCode(object):
    def __init__(self, code):
        self.code = code
        self.invokes = ""
        
        # Find all the function name(  lines in the Javasript code
        for line in code.split("\n"):
            line = line.strip().split()
            if line and line[0] == 'function':
                funcname = line[1].split("(")[0]
                
                # For each create a bound method to add to the list of invocations
                def invoke(self, *args):
                    argstr = ','.join([json.dumps(arg) for arg in args])
                    self.invokes += "%s(%s);\n" % (funcname, argstr)                

                setattr(self, funcname, types.MethodType(invoke, self))    
                
    def _repr_javascript_(self):
        """Return the code definitions and all invocations"""
        return self.code + self.invokes

For example:


In [15]:
code = JSCode("""
    function test() {
        console.log("test!");
    }

    function output(string) {
        element.append(string);
    }
""")

Now, the code object has test() and output() methods corresponding to Javascript methods:


In [16]:
code.test, code.output


Out[16]:
(<bound method ?.invoke of <__main__.JSCode object at 0x7fcf00414b90>>,
 <bound method ?.invoke of <__main__.JSCode object at 0x7fcf00414b90>>)

When these methods are invoked, corresponding Javascript calls are added to the code object:


In [17]:
code.output("foo")
code.output(" and bar")
code


Out[17]:

Here is the corresponding Javascript fragment that was generated:


In [18]:
print code._repr_javascript_()


    function test() {
        console.log("test!");
    }

    function output(string) {
        element.append(string);
    }
output("foo");
output(" and bar");

References


In [ ]: